home *** CD-ROM | disk | FTP | other *** search
- /* FlexAlert.c - flexible alert routine.
- - sizes alert window to fit the text
- - automatic screen positioning
- - choice of buttons:justOK, okCancel, cancelOK
- - choice of icons, or no icon
- Compiler: Think C 3 or 4. Note if you're not having
- the <MacHeaders> file included automatically this file
- requires the same standard includes necessary to call
- an alert in the standard way.
- *******************************
- IMPORTANT ASSUMPTION: that you have called TEInit() at startup
- but then you knew that didn't you?
- RESTRICTIONS ON USAGE (ie tiny bugs):
- DO NOT USE for out-of-memory alerts, as FlexAlert()
- uses a bit of memory temporarily.
- Draws to the main screen only.
- Clipping of the text message will occur;
- - with no carriage returns in the message, at
- about 850 chars with no icon, 750 with an icon
- - sooner if you have carriage returns; with
- STDTOP = 100 you get 12 lines max.
- FlexAlert() requires 3 ALRT's and 3 DITL's in resources (see below).
- **********************************/
-
- #include "FlexAlert.h"
-
- /* #include <string.h> if you use strlen()
- rather than stringLength() - see below.*/
-
- #ifndef nil
- #define nil 0L
- #endif
-
- /* Change these three numbers to match the 3 ALRT's and DITL's you create.
- You'll find instructions on creating them in the text above, or if you're
- looking at a source code disk you'll find them in the file
- FlexAlert.rsrc */
- #define JUST_OKALERTID 274 /* OK button as item (1), stat text as item (2) */
- #define OK_CANALERTID 272 /* OK(1), Cancel(2), stat text(3) */
- #define CAN_OKALERTID 273 /* Cancel(1), OK(2), stat text(3) */
-
- #define STDTOP 100 /* top of alert on small screen */
- #define ALRTTOP STDTOP /* the old-fashioned way */
- /*
- or, if you are a stickler about user interface guidelines, you
- will want one-third of the grey area above the alert, roughly:
- #define TOPADJUST (342 - 3*STDTOP)
- #define SCRNH screenBits.bounds.bottom - screenBits.bounds.top
- #define ALRTTOP (SCRNH - TOPADJUST)/3
- This is a compromise, to avoid alerts bouncing up and down the screen.
- */
- #define STDW 512 /* standard small screen width */
- #define STDH (342 + ALRTTOP - STDTOP) /* adjusted small screen height */
- #define LEFTINSET 5 /* gap between text and window edge */
- #define TOPINSET 5
- #define ICONPIX 52 /* horizontal pixels needed for icon */
- #define BUTH 16 /* height of OK, Cancel buttons */
- #define BUTW 60 /* width of buttons */
- #define BUTGAP 20 /* space between OK and Cancel */
- #define MAXALERTTEXT 1020L /* characters in alert - four pascal strings */
-
- static char *NilAlertString = "\P";
-
- /* Functions defined in this file - FlexAlert() is the one to call. */
- /*int FlexAlert(int buttonMode, int whichIcon, Ptr csPtr);*/
- /* support functions */
- static void GetTextSize(int maxTextW, int maxTextH,int *statWPtr, int *statHPtr, Ptr csPtr);
- static void SetParamText(Ptr csPtr, Ptr paramPtr);
- /* general utilities - you may want to roll your own */
- int GetNumLines(TEHandle theTE); /* (**TEH).nLines isn't always right */
- long stringLength(char *s); /* just a version of strlen() */
-
- /* FlexAlert(); calls one of three alerts, sizing alert to fit the text.
- buttonMode:0 = justOK (just an OK button)
- 1 = okCancel (OK and Cancel buttons, default is OK)
- 2 = cancelOK (default is Cancel)
- whichIcon: 0,1,2 = stopIcon, noteIcon, cautionIcon respectively,
- any other value means no icon
- csPtr is a pointer to a C string, absolute maximium of 1020 characters.
- Returns number of button hit (1 or 2, 1 being default) or 0 if error,
- or -1 if the alert stage was not drawn.
- Note that a returned value of 1 means OK if buttonMode is okCancel,
- and means Cancel if buttonMode is cancelOK.
- The alert is drawn 'ALRTTOP' pixels down from the top of the screen,
- and is centered left-right. The size of the alert is restricted so
- that it will always fit on a Mac Plus screen, allowing you to check that
- text is not clipped when using any size of main screen.
- Typical call;
- static char *stopMessage = "Are you seeing a stop alert with \
- an OK button as default, and also a Cancel button?";
- int buttonHit;
- if (!(buttonHit = FlexAlert(okCancel, stopIcon, stopMessage)))
- - alert could not be shown due to an error of some sort;
- else if (buttonHit == 1) - the default (OK);
- else if (buttonHit == 2) - the other button (here Cancel);
- else if (buttonHit == -1) - the alert was not drawn; if you
- set the alerts to be drawn all 4 stages this won't happen.
- **************************************/
- int FlexAlert(int buttonMode, int whichIcon, Ptr csPtr)
- {
- int resID;
- Rect *drp, *rPtr;
- AlertTHndl alrtHandle;
- Handle itemH, paramH;
- int i, tempOff, totalItems, numButtons,
- w, hitItem, actualW,
- maxTextH, maxTextW, alrtLeft, alrtTop,
- statW, statH;
- Boolean hasIcon;
-
- actualW = screenBits.bounds.right - screenBits.bounds.left;
- alrtTop = ALRTTOP; /* nominally 100 */
-
- if (buttonMode == okCancel)
- {
- resID = OK_CANALERTID;
- numButtons = 2;
- }
- else if (buttonMode == cancelOK)
- {
- resID = CAN_OKALERTID;
- numButtons = 2;
- }
- else /* justOK by default */
- {
- resID = JUST_OKALERTID;
- numButtons = 1;
- }
- /* Load ALRT and DITL for adjustment before showing */
- if (!(alrtHandle = (AlertTHndl)GetResource('ALRT', resID))
- || !(itemH = GetResource('DITL', resID))
- || ResError())
- return(0);
-
- if (whichIcon == stopIcon || whichIcon == noteIcon
- || whichIcon == cautionIcon)
- hasIcon = TRUE;
- else
- hasIcon = FALSE;
-
- /* Determine maximum room available for stat text box in alert. */
- /* restrict maximum size to fit on Mac Plus screen */
- /* horizontal layout: gap, (icon), alert left,
- gap, text, gap, alert right, gap */
- if (!hasIcon)
- maxTextW = STDW - 4*LEFTINSET;
- else
- maxTextW = STDW - 4*LEFTINSET - ICONPIX;
- /* vertical layout: down to ALRTTOP, followed by
- alert top, gap, text, two gaps, buttons, gap, alert bottom, gap */
- maxTextH = STDH - alrtTop - 5*TOPINSET - BUTH;
-
- /* If no room for text, return error */
- if (maxTextW <= 0 || maxTextH <= 0)
- return(0);
-
- /* Determine statW, statH, the width and height of
- the static text box. */
- GetTextSize(maxTextW, maxTextH, &statW, &statH, csPtr);
- if (statW <= 0 || statH <= 0)
- return(0);
-
- /* Given autoPlaceIt, statW, statH, hasIcon, set alrt window
- position and size. */
- drp = &((**alrtHandle).boundsRect);
- if (!hasIcon)
- w = statW + 2*LEFTINSET;
- else
- w = statW + 2*LEFTINSET + ICONPIX;
- drp->left = (actualW - w)/2;
- drp->right = drp->left + w;
- drp->top = alrtTop;
- drp->bottom = alrtTop + statH + 4*TOPINSET + BUTH;
-
- /* Adjust rects for buttons and stat text in alert.
- See eg MacTutor September 89 pg 83 for DITL details. */
- tempOff = 6;
- totalItems = *((int *)(*itemH)) + 1;
- for (i = 0; i < totalItems; ++i)
- {
- rPtr = (Rect *)(*itemH + tempOff);
- if (i == 0) /* first button, always present */
- {
- rPtr->top = drp->bottom - drp->top - TOPINSET - BUTH;
- if (numButtons == 2)
- w = (drp->right - drp->left - BUTGAP - 2*BUTW)/2
- + LEFTINSET;
- else
- w = (drp->right - drp->left - BUTW)/2;
- rPtr->left = w;
- rPtr->bottom = rPtr->top + BUTH;
- rPtr->right = w + BUTW;
- }
- else if (i == 1 && numButtons == 2) /* second button if present */
- {
- rPtr->top = drp->bottom - drp->top - TOPINSET - BUTH;
- rPtr->left = w + BUTW + BUTGAP; /* w was set in i == 0 */
- rPtr->bottom = rPtr->top + BUTH;
- rPtr->right = rPtr->left + BUTW;
- }
- else /* stat text by default, i = 1 or 2 */
- {
- rPtr->top = TOPINSET;
- if (!hasIcon)
- rPtr->left = LEFTINSET;
- else
- rPtr->left = LEFTINSET + ICONPIX;
- rPtr->bottom = TOPINSET + statH + 3;
- rPtr->right = rPtr->left + statW;
- }
- tempOff += 8;
- tempOff += 1;
- tempOff += (int)(*((Byte *)(*itemH + tempOff))) + 1;
- if (tempOff & 1)
- ++tempOff;
- tempOff += 4;
- }
-
- /* Set param text and call alert. */
- paramH = NewHandle(stringLength(csPtr) + 4);
- if (MemError() != noErr)
- return(0);
- HLock(paramH);
- SetParamText(csPtr, *paramH);
-
- if (whichIcon == stopIcon)
- hitItem = StopAlert(resID, nil);
- else if (whichIcon == noteIcon)
- hitItem = NoteAlert(resID, nil);
- else if (whichIcon == cautionIcon)
- hitItem = CautionAlert(resID, nil);
- else
- hitItem = Alert(resID, nil);
- HUnlock(paramH);
- DisposHandle(paramH);
- return(hitItem);
- }
-
- /* If you take away from the smallest Mac screen the little bits of it needed
- for the alert buttons, icon, separating gaps, and the menu bar, you have left
- the maximum area of screen available on any Mac for placing the alert text, passed to
- this function in maxTextW, maxTextH. GetTextSize() calculates a nice minimal
- rectangle in which to pour the alert text, returning the width and height
- in statWPtr, statHPtr. After this function is called, the "little bits" are
- added back around the text box when placing the alert and its buttons, icon etc
- on the screen. The result of this approach is that GetTextSize() is relatively
- general and simple - with a bit of "fine tuning" you could probably use it for
- other purposes. Fiddly note: add 3 or so pixels to the height returned by this
- function to prevent the last line of text being clipped.
- *************************************/
- static void GetTextSize(int maxTextW, int maxTextH,int *statWPtr, int *statHPtr, Ptr csPtr)
- {
- GrafPtr savePort;
- Handle dumH,textH;
- Rect dumR;
- WindowPtr dumW;
- TEHandle dumTE;
- FontInfo tempInfo;
- long textLen;
- int lineHeight, statW, statH, curH;
- Boolean foundGoodSize;
-
- SetRect(&dumR, 100,100,200,200);
- /* Create a dummy window, never shown on-screen. */
- GetPort(&savePort);
- dumW = NewWindow(nil,&dumR,"\P",FALSE,0,-1L,FALSE,0L);
- SetPort(dumW);
- /* Copy text into a handle for use with a TEHandle. */
- textLen = stringLength(csPtr);
- if (textLen > MAXALERTTEXT)
- textLen = MAXALERTTEXT;
- textH = NewHandle(textLen);
- if (MemError() != noErr)
- { /* pretend nothing bad happened */
- *statWPtr = maxTextW;
- *statHPtr = maxTextH;
- DisposeWindow(dumW);
- SetPort(savePort);
- return;
- }
- BlockMove(csPtr, *textH, textLen);
- TextFont(0);
- TextSize(0);
- TextFace(0);
- GetFontInfo(&tempInfo);
- lineHeight = tempInfo.ascent + tempInfo.descent + tempInfo.leading;
- /* trim max height to an integral number of lines */
- maxTextH = (maxTextH/lineHeight)*lineHeight;
- /* start with smallest reasonable alert and work up */
- statH = 6*lineHeight; /* about 72 */
- statW = 12*lineHeight; /* about 144 */
- if (statW >= maxTextW)
- statW = maxTextW;
- if (statH >= maxTextH)
- {
- statW = maxTextW;
- statH = maxTextH;
- }
- /* Do the "drawing" of text in the next building */
- dumR.top = 20000;
- dumR.left = 20000;
- dumR.right = dumR.left + statW;
- dumR.bottom = dumR.top + 100;
- dumTE = TENew(&dumR, &dumR);
- /* An instance of what I like to call
- "defensive stupidity" here follows: */
- if ((**dumTE).hText)
- { /* I don't know and I don't care */
- dumH = (**dumTE).hText;
- DisposHandle(dumH);
- }
- (**dumTE).hText = textH;
- (**dumTE).teLength = textLen;
-
- /* For your viewing entertainment you might like
- to play with the algorithm in the following while
- loop: the goal is to experimentally draw the text in
- a variety of rectangles until a small rectangle with
- a pleasing shape is found. The approach at present is;
- start with a box 6 lines high and grow it a line
- at a time until the text fits. The width is the minimum
- of two times the height versus the maximum width
- available. For clarity a 'last resort' loop is shown
- separately - the 2:1 apect ratio is abandoned.
- Text will be clipped if we run off the
- bottom, typically at over 750 characters.
- */
- foundGoodSize = FALSE;
- while (!foundGoodSize)
- {
- (**dumTE).viewRect.right = 20000 + statW;
- (**dumTE).destRect.right = 20000 + statW;
- TECalText(dumTE);
- curH = GetNumLines(dumTE)*lineHeight;
- if (curH <= statH)
- {
- statH = curH;
- foundGoodSize = TRUE;
- }
- else /* try again */
- {
- statH += lineHeight;
- if (statH >= maxTextH)
- { /* running off the bottom */
- statH = maxTextH;
- foundGoodSize = TRUE;/* well, half-true */
- }
- statW = statH*2;
- if (statW >= maxTextW)
- statW = maxTextW;
- }
- }
- /* A 'last resort' attempt; if there is room to grow
- to the right, use it, at the expense of wrecking the
- 2:1 aspect ratio of the text box. */
- if (curH > maxTextH && statW < maxTextW)
- {
- foundGoodSize = FALSE;
- while (!foundGoodSize)
- {
- statW += 3*lineHeight;
- if (statW >= maxTextW)
- {
- statW = maxTextW;
- break; /* ie give up */
- }
- else
- {
- (**dumTE).viewRect.right = 20000 + statW;
- (**dumTE).destRect.right = 20000 + statW;
- TECalText(dumTE);
- curH = GetNumLines(dumTE)*lineHeight;
- if (curH <= statH)
- {
- statH = curH;
- foundGoodSize = TRUE;
- }
- }
- } /* while */
- }
- /* Clean up, and return the best values found */
- *statWPtr = statW;
- *statHPtr = statH;
- TEDispose(dumTE);
- DisposeWindow(dumW);
- SetPort(savePort);
- }
-
- /* csPtr is a c-string; paramPtr is a pointer to a locked handle which will
- hold the four paramtext strings (pascal format) and must be properly
- sized beforehand. The ParamText pointers are progressively set up and text
- is poured from the c-string into the locked handle as consecutive pascal
- strings, with any unused ParamText pointers left pointing to a zero-length string.
- *****************************/
- static void SetParamText(Ptr csPtr, Ptr paramPtr)
- {
- Ptr p[4];
- long csLen;
- int curParamLen, csPos, i;
-
- p[0] = p[1] = p[2] = p[3] = NilAlertString;
- csLen = stringLength(csPtr);
- if (csLen > MAXALERTTEXT) /* 1020 absolute maximum */
- csLen = MAXALERTTEXT;
- /* csPos is index into csPtr, range 0:csLen-1 */
- csPos = 0;
- /* curParamLen indexes a particular pascal string
- after the len byte, range 0:254 */
- curParamLen = 0;
- i = 0; /* index into p[], range 0:3 */
- while (i < 4 && csPos < csLen)
- {
- p[i] = paramPtr + csPos + i;
- while (curParamLen < 255 && csPos < csLen)
- {
- *(p[i] + curParamLen + 1) = *(csPtr + csPos);
- ++csPos;
- ++curParamLen;
- }
- *(p[i]) = curParamLen;
- ++i;
- curParamLen = 0;
- }
- ParamText(p[0],p[1],p[2],p[3]);
- }
-
- /* This function returns at least 1 no matter what, and
- corrects (**TEH).nLines if the last character is a Return.
- ******************************/
- int GetNumLines(TEHandle TEH)
- {
- Handle hText;
- long lastChar;
- int nLines;
-
- nLines = (**TEH).nLines;
- hText = TEGetText(TEH);
- lastChar = GetHandleSize(hText) - 1L;
- if (lastChar < 0) return(1);
- if (*(*hText + lastChar) == 0x0D) /* a Return */
- ++nLines;
- if (nLines <= 0)
- nLines = 1;
- return(nLines);
- }
-
- /* If you are willing to #include <string.h> you can replace the
- three calls above to stringLength() with strlen().
- *****************************/
- long stringLength(char *s)
- {
- long len = 0L;
-
- while (*s++)
- ++len;
- return(len);
- }
-